package org.dromara.common.mybatis.handler

import cn.hutool.core.collection.CollUtil
import io.github.oshai.kotlinlogging.KotlinLogging
import lombok.AllArgsConstructor
import net.sf.jsqlparser.JSQLParserException
import net.sf.jsqlparser.expression.Expression
import net.sf.jsqlparser.expression.operators.conditional.AndExpression
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList
import net.sf.jsqlparser.parser.CCJSqlParserUtil
import org.dromara.common.core.domain.model.LoginUser
import org.dromara.common.core.exception.ServiceException
import org.dromara.common.core.utils.SpringUtils.getBeanFactory
import org.dromara.common.core.utils.StreamUtils.join
import org.dromara.common.core.utils.StringUtils
import org.dromara.common.mybatis.annotation.DataColumn
import org.dromara.common.mybatis.annotation.DataPermission
import org.dromara.common.mybatis.enums.DataScopeType
import org.dromara.common.mybatis.enums.DataScopeType.Companion.findCode
import org.dromara.common.mybatis.helper.DataPermissionHelper
import org.dromara.common.mybatis.helper.DataPermissionHelper.getPermission
import org.dromara.common.mybatis.helper.DataPermissionHelper.getVariable
import org.dromara.common.mybatis.helper.DataPermissionHelper.ignore
import org.dromara.common.mybatis.helper.DataPermissionHelper.setVariable
import org.dromara.common.satoken.utils.LoginHelper.getLoginUser
import org.dromara.common.satoken.utils.LoginHelper.isSuperAdmin
import org.dromara.common.satoken.utils.LoginHelper.isTenantAdmin
import org.springframework.context.expression.BeanFactoryResolver
import org.springframework.expression.*
import org.springframework.expression.common.TemplateParserContext
import org.springframework.expression.spel.standard.SpelExpressionParser
import org.springframework.expression.spel.support.StandardEvaluationContext
import java.util.*
import java.util.function.Function

/**
 * 数据权限过滤
 *
 * @author Lion Li
 * @version 3.5.0
 * @updater LikeYouDo
 * @date 2025/1/27 2:51
 */
open class PlusDataPermissionHandler {

    /**
     * spel 解析器
     */
    private val parser: ExpressionParser = SpelExpressionParser()
    private val parserContext: ParserContext = TemplateParserContext()

    /**
     * bean解析器 用于处理 spel 表达式中对 bean 的调用
     */
    private val beanResolver: BeanResolver = BeanFactoryResolver(getBeanFactory())

    /**
     * 获取数据过滤条件的 SQL 片段
     *
     * @param where             原始的查询条件表达式
     * @param isSelect          是否为查询语句
     * @return 数据过滤条件的 SQL 片段
     */
    fun getSqlSegment(where: Expression?, isSelect: Boolean): Expression? {
        try {
            // 获取数据权限配置
            val dataPermission: DataPermission? = getDataPermission()

            // 获取当前登录用户信息
            var currentUser = getVariable<LoginUser?>("user")
            if (currentUser == null) {
                currentUser = getLoginUser()
                setVariable("user", currentUser)
            }

            // 如果是超级管理员或租户管理员，则不过滤数据
            if (isSuperAdmin() || isTenantAdmin()) {
                return where
            }

            // 构造数据过滤条件的 SQL 片段
            val dataFilterSql = buildDataFilter(dataPermission, isSelect)
            if (StringUtils.isBlank(dataFilterSql)) {
                return where
            }
            val expression = CCJSqlParserUtil.parseExpression(dataFilterSql)
            // 数据权限使用单独的括号 防止与其他条件冲突
            val parenthesis = ParenthesedExpressionList(expression)
            return if (where !== null) {
                AndExpression(where, parenthesis)
            } else {
                parenthesis
            }
        } catch (e: JSQLParserException) {
            throw ServiceException("数据权限解析异常 => " + e.message)
        } finally {
            DataPermissionHelper.removePermission();
        }
    }

    /**
     * 构建数据过滤条件的 SQL 语句
     *
     * @param dataPermission 数据权限注解
     * @param isSelect       标志当前操作是否为查询操作，查询操作和更新或删除操作在处理过滤条件时会有不同的处理方式
     * @return 构建的数据过滤条件的 SQL 语句
     * @throws ServiceException 如果角色的数据范围异常或者 key 与 value 的长度不匹配，则抛出 ServiceException 异常
     */
    private fun buildDataFilter(dataPermission: DataPermission?, isSelect: Boolean): String {
        // 更新或删除需满足所有条件
        var joinStr = if (isSelect) " OR " else " AND "
        if (dataPermission != null && StringUtils.isNotBlank(dataPermission.joinStr)) {
            joinStr = " " + dataPermission.joinStr + " "
        }
        val user = getVariable<LoginUser>("user")
        val defaultValue: Any = "-1"
        val context = NullSafeStandardEvaluationContext(defaultValue)
        context.addPropertyAccessor(
            NullSafePropertyAccessor(context.getPropertyAccessors()[0], defaultValue)
        )
        context.beanResolver = beanResolver
        DataPermissionHelper.getContext().forEach { (name: String?, value: Any?) -> context.setVariable(name, value) }
        val conditions: MutableSet<String?> = HashSet()

        // 优先设置变量
        val keys: MutableList<String?> = ArrayList<String?>()
        val ignoreMap: MutableMap<DataColumn?, Boolean?> = HashMap<DataColumn?, Boolean?>()
        for (dataColumn in dataPermission!!.value) {
            if (dataColumn.key.size != dataColumn.value.size) {
                throw ServiceException("角色数据范围异常 => key与value长度不匹配")
            }
            // 包含权限标识符 这直接跳过
            if (StringUtils.isNotBlank(dataColumn.permission) &&
                CollUtil.contains(user!!.menuPermission, dataColumn.permission)
            ) {
                ignoreMap[dataColumn] = true
                continue
            }
            // 设置注解变量 key 为表达式变量 value 为变量值
            for (i in dataColumn.key.indices) {
                context.setVariable(dataColumn.key[i], dataColumn.value[i])
            }
            keys.addAll(Arrays.stream<String>(dataColumn.key).map<String> { key: String? -> "#$key" }.toList())
        }

        if (user != null) {
            for ((roleId, _, _, dataScope) in user.roles!!) {
                user.roleId = roleId
                // 获取角色权限泛型
                val type = dataScope?.let { findCode(it) }
                if (type === null) {
                    throw ServiceException("角色数据范围异常 => $dataScope")
                }
                // 全部数据权限直接返回
                if (type == DataScopeType.ALL) {
                    return StringUtils.EMPTY
                }
                var isSuccess = false
                for (dataColumn in dataPermission.value) {
                    // 包含权限标识符 这直接跳过
                    if (ignoreMap.containsKey(dataColumn)) {
                        // 修复多角色与权限标识符共用问题 https://gitee.com/dromara/RuoYi-Vue-Plus/issues/IB4CS4
                        conditions.add("$joinStr 1 = 1 ")
                        isSuccess = true
                        continue
                    }
                    // 不包含 key 变量 则不处理
                    if (!StringUtils.containsAny(type.sqlTemplate, *(keys.toTypedArray()))) {
                        continue
                    }
                    // 当前注解不满足模板 不处理
                    if (!StringUtils.containsAny(type.sqlTemplate, *dataColumn.key)) {
                        continue
                    }
                    // 忽略数据权限 防止spel表达式内有其他sql查询导致死循环调用
                    val sql = ignore {
                        parser.parseExpression(type.sqlTemplate, parserContext)
                            .getValue(context, String::class.java)
                    }
                    // 解析sql模板并填充
                    conditions.add(joinStr + sql)
                    isSuccess = true
                }
                // 未处理成功则填充兜底方案
                if (!isSuccess && StringUtils.isNotBlank(type.elseSql)) {
                    conditions.add(joinStr + type.elseSql)
                }
            }
        }

        if (CollUtil.isNotEmpty(conditions)) {
            val sql = join(conditions, Function.identity(), "")
            return sql.substring(joinStr.length)
        }
        return StringUtils.EMPTY
    }

    /**
     * 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
     *
     * @return DataPermission 注解对象，如果不存在则返回 null
     */
    fun getDataPermission(): DataPermission? {
        return getPermission()
    }

    /**
     * 检查给定的映射语句 ID 是否有效，即是否能够找到对应的 DataPermission 注解对象
     *
     * @return 如果找到对应的 DataPermission 注解对象，则返回 false；否则返回 true
     */
    fun invalid(): Boolean {
        return getDataPermission() == null
    }

    companion object {
        private val log = KotlinLogging.logger { }

        /**
         * 对所有null变量找不到的变量返回默认值
         */
        private class NullSafeStandardEvaluationContext(
            private val defaultValue: Any
        ) : StandardEvaluationContext() {

            override fun lookupVariable(name: String): Any {
                val obj = super.lookupVariable(name) ?: return defaultValue
                // 如果读取到的值是 null，则返回默认值
                return obj
            }
        }

        /**
         * 对所有null变量找不到的变量返回默认值 委托模式 将不需要处理的方法委托给原处理器
         */
        private class NullSafePropertyAccessor(
            private val delegate: PropertyAccessor,
            private val defaultValue: Any
        ) : PropertyAccessor {

            override fun getSpecificTargetClasses(): Array<Class<*>>? {
                return delegate.specificTargetClasses
            }

            @Throws(AccessException::class)
            override fun canRead(context: EvaluationContext, target: Any?, name: String): Boolean {
                return delegate.canRead(context, target, name)
            }

            @Throws(AccessException::class)
            override fun read(context: EvaluationContext, target: Any?, name: String): TypedValue {
                val value = delegate.read(context, target, name)
                // 如果读取到的值是 null，则返回默认值
                if (value.value == null) {
                    return TypedValue(defaultValue)
                }
                return value
            }

            @Throws(AccessException::class)
            override fun canWrite(context: EvaluationContext, target: Any?, name: String): Boolean {
                return delegate.canWrite(context, target, name)
            }

            @Throws(AccessException::class)
            override fun write(context: EvaluationContext, target: Any?, name: String, newValue: Any?) {
                delegate.write(context, target, name, newValue)
            }
        }
    }

}
